package com.itbaizhan.itbaizhanlock.service.impl;

import com.itbaizhan.itbaizhanlock.entity.OrderItem;
import com.itbaizhan.itbaizhanlock.entity.Product;
import com.itbaizhan.itbaizhanlock.entity.TOrder;
import com.itbaizhan.itbaizhanlock.mapper.OrderItemMapper;
import com.itbaizhan.itbaizhanlock.mapper.ProductMapper;
import com.itbaizhan.itbaizhanlock.mapper.TOrderMapper;
import com.itbaizhan.itbaizhanlock.service.ITOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.itbaizhan.itbaizhanlock.utils.DistributedRedisLock;
import lombok.extern.slf4j.Slf4j;


import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author itbaizhan
 * @since 05-27
 */
@Service
@Slf4j
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {
    //声明事务管理器
    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    //引入redis控制器  输入StringRedisTemplate可能没有提示报红，直接输入就行
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //使用redisson
    @Autowired
    private DistributedRedisLock distributedRedisLock;

    //用于使用zookeeper
    @Autowired
    private CuratorFramework curatorFramework;

    @Resource
    ProductMapper productMapper;

    @Resource
    OrderItemMapper orderItemMapper;

    /**
     * 创建订单  创建订单表，也要创建订单与商品的关联表
     * @param productId  商品id
     * @param purchaseNum  购买数量
     * @return
     * 因为有更新库存操作，创建订单、创建订单和商品关联三步操作，所以要保证一致性
     * 要加上@Transactional
     * 在public 后面加上synchronized ,能使发送进来的请求，一个一个地进入程序运行，
     * 不会当有多个请求进入时会，大多数请求成功，那么超过商品数量时，依然会请求成功,
     * 但当加了synchronized 依然会发生超卖问题，当库存是2时，有5个请求进入，会有三个成功，
     * 因为当运行了全部代码，synchronized锁会被释放，下一个请求进入，然而这时候的事务（Transactional）都没有提交完成，
     * 就进入下一个请求，那么这个请求所获得的库存数量依然是上一个请求的数量，所以不可
     * 以用Transactional，要手动提交,使用PlatformTransactionManager与synchronized
  */
    //@Transactional(rollbackFor = Exception.class)
    @Override
    public synchronized String createOrder(Integer productId, Integer purchaseNum) {
        //事务
        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);

        //1根据商品的id 获取商品信息
        Product product = productMapper.selectById(productId);
        //2判断商品是否存在
        if(product == null ){
            //有异常都要回滚
            platformTransactionManager.rollback(transaction);
            throw new RuntimeException("购买商品不存在");
        }
        //查看每个线程的库存数量
       // log.info(Thread.currentThread().getName()+"库存数量"+product.getCount());
        //3校验库存
        if(purchaseNum > product.getCount()){
            //有异常都要回滚
            platformTransactionManager.rollback(transaction);
            throw new RuntimeException("库存不足");
        }
        //4成功购买后更新库存操作
        int count = product.getCount() - purchaseNum;
        product.setCount(count);
        productMapper.updateById(product);
        //创建订单
        TOrder order = new TOrder();
      //  order.setId(); 在实体类添加了雪花算法 ，自动生成id，所以不用在这里设定
        order.setOrderStatus(1);  //订单状态
        order.setReceiverName("张三");
        order.setReceiverMobile("13576287332");
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum))); //订单价格
        //放进数据库
        baseMapper.insert(order);
        //6.创建订单和商品关联
        OrderItem orderItem = new OrderItem();
        //id也用雪花算法
        orderItem.setOrderId(order.getId());
        orderItem.setProduceId(product.getId());
        orderItem.setPurchaseNum(product.getCount());
        orderItem.setPurchasePrice(product.getPrice());
        orderItemMapper.insert(orderItem);
        //提交事务
        platformTransactionManager.commit(transaction);
        return order.getId();
    }




    //上面的手动提交事务解决不了下面的问题
    //如果是两台jvm同时运行，用nginx反向代理，有可能会发生错误，例如：库存有2，同时发送
    //请求50，可能会产生4 个订单
    /**
     * 创建订单，用悲观锁实现   在mysql 语句 加无精  for update
     * 解决多个jvm运行时，超卖问题
     * @param productId
     * @param purchaseNum
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String createOrderPessimisticlock(Integer productId, Integer purchaseNum) {
        //1根据商品的id 获取商品信息
        Product product = productMapper.findByID(productId);
        //2判断商品是否存在
        if(product == null ){

            throw new RuntimeException("购买商品不存在");
        }
        //查看每个线程的库存数量
        // log.info(Thread.currentThread().getName()+"库存数量"+product.getCount());
        //3校验库存
        if(purchaseNum > product.getCount()){

            throw new RuntimeException("库存不足");
        }
        //4成功购买后更新库存操作
        int count = product.getCount() - purchaseNum;
        product.setCount(count);
        productMapper.updateById(product);
        //创建订单
        TOrder order = new TOrder();
        //  order.setId(); 在实体类添加了雪花算法 ，自动生成id，所以不用在这里设定
        order.setOrderStatus(1);  //订单状态
        order.setReceiverName("张三");
        order.setReceiverMobile("13576287332");
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum))); //订单价格
        //放进数据库
        baseMapper.insert(order);
        //6.创建订单和商品关联
        OrderItem orderItem = new OrderItem();
        //id也用雪花算法
        orderItem.setOrderId(order.getId());
        orderItem.setProduceId(product.getId());
        orderItem.setPurchaseNum(product.getCount());
        orderItem.setPurchasePrice(product.getPrice());
        orderItemMapper.insert(orderItem);

        return order.getId();
    }


    /**
     * 创建订单，用乐观锁实现
     * 解决多个jvm运行时，超卖问题
     * @param productId
     * @param purchaseNum
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String createOrderOptimisticlock(Integer productId, Integer purchaseNum) {
        //用于乐观锁
        int retryCount = 0; //重试次数
        int update = 0;  //更新结果

        //1根据商品的id 获取商品信息
        Product product = productMapper.selectById(productId);
        //2判断商品是否存在
        if(product == null ){

            throw new RuntimeException("购买商品不存在");
        }
        //查看每个线程的库存数量
        // log.info(Thread.currentThread().getName()+"库存数量"+product.getCount());
        //3校验库存
        if(purchaseNum > product.getCount()){

            throw new RuntimeException("库存不足");
        }
        /*乐观锁更新库存
        * 更新失败，说明其他线程已经被修改数据，本地扣减库存失败，可以重试
        * 最多重试三次*/

        while (retryCount <3 && update ==0){
            //更新操作  返回1
            update = this.reduceCount(productId, purchaseNum);

            retryCount++;
        }

        if(update == 0){
            throw new RuntimeException("库存不够");
        }
        //创建订单
        TOrder order = new TOrder();
        //  order.setId(); 在实体类添加了雪花算法 ，自动生成id，所以不用在这里设定
        order.setOrderStatus(1);  //订单状态
        order.setReceiverName("张三");
        order.setReceiverMobile("13576287332");
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum))); //订单价格
        //放进数据库
        baseMapper.insert(order);
        //6.创建订单和商品关联
        OrderItem orderItem = new OrderItem();
        //id也用雪花算法
        orderItem.setOrderId(order.getId());
        orderItem.setProduceId(product.getId());
        orderItem.setPurchaseNum(product.getCount());
        orderItem.setPurchasePrice(product.getPrice());
        orderItemMapper.insert(orderItem);

        return order.getId();
    }
    /**
     *减库存
     * mysql 默认事务隔离级别是可重复读，会导致在同一个事务里面查询Product product = productMapper.selectById(productId);
     * 得到的数据是相同的，所以提供一个新的方法，每次循环都启动一个新的事务，
     * 扣减库存的操作
     * @param id  商品id
     * @param count  购买数量
     * @return
     */
    //封装更新库存方法
    @Transactional(rollbackFor = Exception.class)
    public int reduceCount(int id,int count){
        int result =0;
        //根据id查询商品，从而获得库存
        Product product = productMapper.selectById(id);

        //判断库存
        if(product.getCount() >= count){
            //减库存，乐观锁
           result =  productMapper.updateProduct(product.getId(),count,product.getVersion());
        }
        return  result;
    }

    /**
     * 用redis创建订单  这是最基本的实现，可能出现问题
     * @param productId
     * @param purchaseNum
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String createOrderRedis(Integer productId, Integer purchaseNum) {
        //1，获得锁 setnx    key  value time type
        String key = "lock:";
        //Thread.currentThread().getId() + ""   加 “ ”  线程id转为正面照
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key + productId, Thread.currentThread().getId() + "", 30, TimeUnit.SECONDS);
        //判断是否获得锁，如果没有获得锁
        if(!result){
            return "不允许重复下单";
        }
        //用try catch finally 因为最后要释放锁
        try {
            //创建成功
            //1根据商品的id 获取商品信息
            Product product = productMapper.selectById(productId);
            //2判断商品是否存在
            if(product == null ){

                throw new RuntimeException("购买商品不存在");
            }
            //查看每个线程的库存数量
            // log.info(Thread.currentThread().getName()+"库存数量"+product.getCount());
            //3校验库存
            if(purchaseNum > product.getCount()){

                throw new RuntimeException("库存不足");
            }
            //4成功购买后更新库存操作
            int count = product.getCount() - purchaseNum;
            product.setCount(count);
            productMapper.updateById(product);
            //创建订单
            TOrder order = new TOrder();
            //  order.setId(); 在实体类添加了雪花算法 ，自动生成id，所以不用在这里设定
            order.setOrderStatus(1);  //订单状态
            order.setReceiverName("张三");
            order.setReceiverMobile("13576287332");
            order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum))); //订单价格
            //放进数据库
            baseMapper.insert(order);
            //6.创建订单和商品关联
            OrderItem orderItem = new OrderItem();
            //id也用雪花算法
            orderItem.setOrderId(order.getId());
            orderItem.setProduceId(product.getId());
            orderItem.setPurchaseNum(product.getCount());
            orderItem.setPurchasePrice(product.getPrice());
            orderItemMapper.insert(orderItem);
            //创建成功返回订单id
            return order.getId();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            /*当有两个线程时，线程1运行到一半阻塞，因为超时而释放锁，那么线程2拿到锁，运行代码，
            当线程2运行中时，线程1运行完代码把锁删除了，这时的锁是线程2的，就会造成锁误删除*/
            //通过stringRedisTemplate.opsForValue().setIfAbsent(key + productId, Thread.currentThread().getId() + "", 30, TimeUnit.SECONDS);
            //知道可以通过redis的key值获得value值，而value是Thread.currentThread().getId() + ""
            //通过对比锁的value与线程组的id是否一致，如果一致才删除锁

            //1，获取锁标识
            String threadIdFlag = stringRedisTemplate.opsForValue().get(key + productId);
            //2获取当前的线程id
            String id = Thread.currentThread().getId()+"";
            //判断标识是否一致
            if(id.equals(threadIdFlag)){
                stringRedisTemplate.delete(key+productId);
            }

        }

        return "创建失败";
    }

    /**
     * 用redission创建订单  修复了上面redis可能出现问题(不可重入，不可重试，超时释放，主从一致性)
     * 性能最高，最常用
     * @param productId
     * @param purchaseNum
     * @return
     */
    @Override
    public String createOrderRedission(Integer productId, Integer purchaseNum) {
        String key = "lock:";
        //productId 是integer类型，所以要转为string,因为lock()，括号里填的是String类型
        Boolean lock = distributedRedisLock.lock(key.concat(productId+""));//创建成功

        if(!lock){
            return "失败";
        }
        try{
            //创建成功
            //1根据商品的id 获取商品信息
            Product product = productMapper.selectById(productId);
            //2判断商品是否存在
            if(product == null ){

                throw new RuntimeException("购买商品不存在");
            }
            //查看每个线程的库存数量
            // log.info(Thread.currentThread().getName()+"库存数量"+product.getCount());
            //3校验库存
            if(purchaseNum > product.getCount()){

                throw new RuntimeException("库存不足");
            }
            //4成功购买后更新库存操作
            int count = product.getCount() - purchaseNum;
            product.setCount(count);
            productMapper.updateById(product);
            //创建订单
            TOrder order = new TOrder();
            //  order.setId(); 在实体类添加了雪花算法 ，自动生成id，所以不用在这里设定
            order.setOrderStatus(1);  //订单状态
            order.setReceiverName("张三");
            order.setReceiverMobile("13576287332");
            order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum))); //订单价格
            //放进数据库
            baseMapper.insert(order);
            //6.创建订单和商品关联
            OrderItem orderItem = new OrderItem();
            //id也用雪花算法
            orderItem.setOrderId(order.getId());
            orderItem.setProduceId(product.getId());
            orderItem.setPurchaseNum(product.getCount());
            orderItem.setPurchasePrice(product.getPrice());
            orderItemMapper.insert(orderItem);
            //创建成功返回订单id
            return order.getId();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            distributedRedisLock.unlock(key.concat(productId+""));
        }
        return  "失败";
    }


    /**
     * 创建订单，zookeeper
     * zookeeper分布式锁可靠性最高
     * 解决多个jvm运行时，超卖问题
     * @param productId  商品id
     * @param purchaseNum  购买数量
     * @return
     */
    @Override
    public String createOrderZookeeper(Integer productId, Integer purchaseNum) throws Exception {
       //InterProcessMutex公平锁
        InterProcessMutex lock = new InterProcessMutex(curatorFramework,"/lockPath");  ///lockPath 是节点要有两个单词
        //lock.acquire 尝试获取锁 等待5s maxWait第一个参数：定义的是时间数字，第二个参数是时间单位
        if(lock.acquire(5,TimeUnit.SECONDS)){
            try {
//创建成功
                //1根据商品的id 获取商品信息
                Product product = productMapper.selectById(productId);
                //2判断商品是否存在
                if(product == null ){

                    throw new RuntimeException("购买商品不存在");
                }
                //查看每个线程的库存数量
                // log.info(Thread.currentThread().getName()+"库存数量"+product.getCount());
                //3校验库存
                if(purchaseNum > product.getCount()){

                    throw new RuntimeException("库存不足");
                }
                //4成功购买后更新库存操作
                int count = product.getCount() - purchaseNum;
                product.setCount(count);
                productMapper.updateById(product);
                //创建订单
                TOrder order = new TOrder();
                //  order.setId(); 在实体类添加了雪花算法 ，自动生成id，所以不用在这里设定
                order.setOrderStatus(1);  //订单状态
                order.setReceiverName("张三");
                order.setReceiverMobile("13576287332");
                order.setOrderAmount(product.getPrice().multiply(new BigDecimal(purchaseNum))); //订单价格
                //放进数据库
                baseMapper.insert(order);
                //6.创建订单和商品关联
                OrderItem orderItem = new OrderItem();
                //id也用雪花算法
                orderItem.setOrderId(order.getId());
                orderItem.setProduceId(product.getId());
                orderItem.setPurchaseNum(product.getCount());
                orderItem.setPurchasePrice(product.getPrice());
                orderItemMapper.insert(orderItem);
                //创建成功返回订单id
                return order.getId();
            }finally {
                lock.release();
            }
        }



        return "创建失败";
    }
}
